package cz.muni.fi.pv112.cranesimulator.scene.handlers;

import cz.muni.fi.pv112.cranesimulator.models.crane.Crane;
import cz.muni.fi.pv112.cranesimulator.models.scene.Container;
import cz.muni.fi.pv112.cranesimulator.models.scene.Floor;
import cz.muni.fi.pv112.cranesimulator.models.scene.Skybox;
import cz.muni.fi.pv112.cranesimulator.scene.Scene;
import cz.muni.fi.pv112.cranesimulator.scene.SceneKeyInputApplier;
import cz.muni.fi.pv112.cranesimulator.scene.items.Camera;
import cz.muni.fi.pv112.cranesimulator.scene.items.CameraInterface;
import cz.muni.fi.pv112.cranesimulator.scene.items.CameraMode;
import cz.muni.fi.pv112.cranesimulator.utils.handlers.inputs.KeyInputHandler;
import cz.muni.fi.pv112.cranesimulator.utils.handlers.inputs.MouseInputHandler;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import javax.media.opengl.GL;
import javax.media.opengl.GL2;
import javax.media.opengl.glu.GLU;
import static cz.muni.fi.pv112.cranesimulator.scene.handlers.ContainerHandler.displayContainers;
import static cz.muni.fi.pv112.cranesimulator.scene.handlers.ContainerHandler.initContainerModels;
import static cz.muni.fi.pv112.cranesimulator.utils.Calculator.moveBy;
import static cz.muni.fi.pv112.cranesimulator.utils.Calculator.rotateAndRecalculateStaticCameraPosition;
import static cz.muni.fi.pv112.cranesimulator.utils.handlers.opengl.DisplayListHandler.deleteDiplayLists;
import static cz.muni.fi.pv112.cranesimulator.utils.handlers.opengl.DisplayListHandler.deleteDisplayList;
import static cz.muni.fi.pv112.cranesimulator.utils.handlers.opengl.ModelDisplayHandler.drawModelFromDisplaylist;

/**
 *
 * @author Michal Ďuriš
 */
public class SceneHandler implements Scene {

    private GLU glu;

    private final SceneKeyInputApplier keyInputApplier;

    private final Skybox skybox = new Skybox();
    private final Floor floor = new Floor(700.0f, 700.0f, 350, 350);

    private final Container[] containers = {new Container(),
                                            new Container(),
                                            new Container(),
                                            new Container()};

    private final CameraHandler cameraHandler = new CameraHandler();
    private final Camera[] cameras = {new Camera(),
                                      new Camera(CameraMode.CABIN_CAMERA_MODE),
                                      new Camera(
                                      CameraMode.LIFTING_ROD_CAMERA_MODE)};

    private final CraneHandler craneHandler = new CraneHandler();

    public SceneHandler() {
        keyInputApplier = new SceneKeyInputApplier(KeyInputHandler.getInstance(),
                this);
    }

    @Override
    public void init(GL gl, GLU glu) {
        this.glu = glu;

        skybox.init(gl);
        floor.init(gl);

        initContainerModels(gl, containers);

        cameraHandler.setKeyInputHandler(KeyInputHandler.getInstance());
        cameraHandler.setSceneBorders(Skybox.HALF_EDGE_LENGTH,
                Skybox.EDGE_LENGTH,
                Skybox.HALF_EDGE_LENGTH);
        cameraHandler.setCamera(cameras[CameraMode.FREE_CAMERA_MODE.ordinal()]);

        craneHandler.init(gl);
    }

    public CameraInterface getActualCamera() {
        return cameraHandler.getCamera();
    }

    public void changeCameraMode(CameraMode cameraMode) {
        cameraHandler.setCamera(cameras[cameraMode.ordinal()]);
    }

    public void moveCameraBy(float x, float y, float z) {
        cameraHandler.moveCameraBy(x, y, z);
    }

    public void rotateCraneLeft() {
        craneHandler.rotateLeftOperatingPart();

        rotateAndRecalculateStaticCameraPosition(
                -craneHandler.getCrane().getOperatingPart().getRotaryAngleStep(),
                cameras[CameraMode.CABIN_CAMERA_MODE.ordinal()]);

        rotateAndRecalculateStaticCameraPosition(
                -craneHandler.getCrane().getOperatingPart().getRotaryAngleStep(),
                cameras[CameraMode.LIFTING_ROD_CAMERA_MODE.ordinal()]);

        if (craneHandler.isLiftingContainer()) {
            craneHandler.getLiftedContainer().rotateBy(0.0f,
                    -craneHandler.getCrane()
                    .getOperatingPart()
                    .getRotaryAngleStep(),
                    0.0f);
        }
    }

    public void rotateCraneRight() {
        craneHandler.rotateRightOperatingPart();

        rotateAndRecalculateStaticCameraPosition(
                craneHandler.getCrane().getOperatingPart().getRotaryAngleStep(),
                cameras[CameraMode.CABIN_CAMERA_MODE.ordinal()]);

        rotateAndRecalculateStaticCameraPosition(
                craneHandler.getCrane().getOperatingPart().getRotaryAngleStep(),
                cameras[CameraMode.LIFTING_ROD_CAMERA_MODE.ordinal()]);

        if (craneHandler.isLiftingContainer()) {
            craneHandler.getLiftedContainer().rotateBy(0.0f,
                    craneHandler.getCrane()
                    .getOperatingPart()
                    .getRotaryAngleStep(),
                    0.0f);
        }
    }

    public void moveLiftingRiderForward() {
        float executedShift = craneHandler.moveLiftingRiderForward();

        if (executedShift != 0.0f) {
            Camera liftingRodCamera = cameras[CameraMode.LIFTING_ROD_CAMERA_MODE
                    .ordinal()];

            liftingRodCamera.setPosition(moveBy(0.0f,
                    executedShift,
                    0.0f,
                    liftingRodCamera.getPosition(),
                    liftingRodCamera
                    .getOrientation()));

            if (craneHandler.isLiftingContainer()) {
                craneHandler.getLiftedContainer().moveBy(0.0f, 0.0f,
                        executedShift);
            }
        }
    }

    public void moveLiftingRiderBackward() {
        float executedShift = craneHandler.moveLiftingRiderBackward();

        if (executedShift != 0.0f) {
            Camera liftingRodCamera = cameras[CameraMode.LIFTING_ROD_CAMERA_MODE
                    .ordinal()];

            liftingRodCamera.setPosition(moveBy(0.0f,
                    -executedShift,
                    0.0f,
                    liftingRodCamera.getPosition(),
                    liftingRodCamera
                    .getOrientation()));

            if (craneHandler.isLiftingContainer()) {
                craneHandler.getLiftedContainer().moveBy(0.0f, 0.0f,
                        -executedShift);
            }
        }
    }

    public void moveMagnetUp() {
        float displacementUp = craneHandler.moveUpLiftingRod();

        if (displacementUp != 0.0f) {
            if (craneHandler.isLiftingContainer()) {
                craneHandler.getLiftedContainer().moveBy(0.0f, displacementUp,
                        0.0f);
            }
        }
    }

    public void moveMagnetDown() {
        float displacementDown = craneHandler.moveDownLiftingRod();

        if (displacementDown != 0.0f) {
            if (craneHandler.isLiftingContainer()) {
                craneHandler.getLiftedContainer()
                        .moveBy(0.0f, -displacementDown, 0.0f);
            }
        }
    }

    public void catchLiftableObject() {
        if (craneHandler.isLiftingContainer()) {
            craneHandler.releaseContainer();
        } else {
            craneHandler.catchContainer(containers);
        }
    }

    @Override
    public void processKeyInput(KeyEvent keyEvent, boolean keyPressed) {
        KeyInputHandler.getInstance().processKeyInput(keyEvent, keyPressed);
        keyInputApplier.applyKeyInputs(false);
    }

    @Override
    public void processMouseInput(MouseEvent mouseEvent, int mouseEventType) {

        MouseInputHandler mouseInputHandler = MouseInputHandler.getInstance();

        mouseInputHandler.processMouseEvent(mouseEvent, mouseEventType);

        if (mouseEventType == MouseEvent.MOUSE_DRAGGED) {
            cameraHandler.rotateCamera(mouseInputHandler.getResult());
        }
    }

    @Override
    public void displayScene(GL2 gl) {
        gl.glDisable(GL2.GL_DEPTH_TEST);
        gl.glDisable(GL2.GL_LIGHTING);
        gl.glDisable(GL2.GL_BLEND);

        keyInputApplier.applyKeyInputs(true);
        cameraHandler.lookAt(glu);

        drawModelFromDisplaylist(gl, skybox);

        gl.glEnable(GL2.GL_DEPTH_TEST);
        gl.glShadeModel(GL2.GL_SMOOTH);

        enableSceneLights(gl);
        enableCraneLights(gl, craneHandler.getCrane());

        drawModelFromDisplaylist(gl, floor);

        craneHandler.displayCrane(gl);

        displayContainers(gl, containers);
    }

    private static void enableSceneLights(GL2 gl) {
        float[] sunPosition = {120, 275, 350, 1};
        float[] sunLightColor = {1.0f, 1.0f, 1.0f, 1.0f};

        gl.glEnable(GL2.GL_LIGHTING);
        gl.glEnable(GL2.GL_LIGHT0);

        gl.glLightfv(GL2.GL_LIGHT0, GL2.GL_POSITION, sunPosition, 0);
        //gl.glLightfv(GL2.GL_LIGHT0, GL2.GL_AMBIENT, lightColor, 0);
        gl.glLightfv(GL2.GL_LIGHT0, GL2.GL_DIFFUSE, sunLightColor, 0);
        gl.glLightfv(GL2.GL_LIGHT0, GL2.GL_SPECULAR, sunLightColor, 0);

    }

    private static void enableCraneLights(GL2 gl, Crane crane) {
        float[] spotPosition = {-37.0f, 195.0f, 0.0f, 1.0f};
        float[] spotDirection = {0.0f, -1.0f, 0.0f};
        float[] spotLightColor = {1.0f, 1.0f, 1.0f, 1.0f};

        int lightNumber = GL2.GL_LIGHT1;

        gl.glEnable(GL2.GL_LIGHTING);
        gl.glEnable(lightNumber);

        gl.glPushMatrix();

        gl
                .glRotatef(crane.getOperatingPart().getRotaryAngle(), 0.0f, 1.0f,
                        0.0f);
        gl.glTranslatef(-crane.getLiftingRider().getShift(), 0.0f, 0.0f);

        gl.glLightfv(lightNumber, GL2.GL_POSITION, spotPosition, 0);
        gl.glLightfv(lightNumber, GL2.GL_AMBIENT, spotLightColor, 0);
        gl.glLightfv(lightNumber, GL2.GL_DIFFUSE, spotLightColor, 0);
        gl.glLightfv(lightNumber, GL2.GL_SPECULAR, spotLightColor, 0);
        gl.glLightf(lightNumber, GL2.GL_SPOT_CUTOFF, 11.0f);
        gl.glLightfv(lightNumber, GL2.GL_SPOT_DIRECTION, spotDirection, 0);
        gl.glLightf(lightNumber, GL2.GL_SPOT_EXPONENT, 1.0f);

        gl.glPopMatrix();
    }

    @Override
    public void unloadModels(GL gl) {
        craneHandler.deleteCraneFromDisplayList(gl);

        deleteDisplayList(gl.getGL2(), skybox);
        deleteDisplayList(gl.getGL2(), floor);
        deleteDiplayLists(gl.getGL2(), containers);

    }

}
